package com.xh3140.android.widget.imageview;

import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.ScaleGestureDetector.OnScaleGestureListener;
import android.view.ViewConfiguration;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;

/**
 * 可缩放图片显示控件
 * source by [https://blog.csdn.net/lovoo/article/details/51629171]
 */
@SuppressWarnings("unused")
public class ScaleImageView extends AppCompatImageView {

    private static final String TAG = "ScaleImageView";

    /**
     * 最小的缩放值
     */
    private float mMinScale;

    /**
     * 最大的缩放值
     */
    private float mMaxScale;

    /**
     * 是否可以拖动
     */
    private boolean mIsCanDrag;

    /**
     * 可移动最短距离限制，大于这个值时就可移动
     */
    private final int mTouchSlop;
    private final Matrix mMatrix = new Matrix();
    private final RectF mBounds = new RectF();
    private final GestureDetector mGesture;
    private final ScaleGestureDetector mScaleGesture;

    private boolean mIsScaling;
    private boolean mIsAutoScaling;

    public ScaleImageView(@NonNull Context context) {
        this(context, null);
    }

    public ScaleImageView(@NonNull Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ScaleImageView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        super.setScaleType(ScaleType.MATRIX);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mGesture = new GestureDetector(context, new OnGestureListenerImpl());
        mScaleGesture = new ScaleGestureDetector(context, new OnScaleGestureListenerImpl());
    }

    /**
     * 获取图片的缩放比例
     */
    public float getImageScale() {
        final float[] values = new float[9];
        mMatrix.getValues(values);
        return values[Matrix.MSCALE_X];
    }

    /**
     * 获取图片的区域范围
     */
    @Nullable
    public RectF getImageBounds() {
        final Drawable drawable = getDrawable();
        if (drawable != null) {
            mBounds.set(drawable.getBounds());
            mMatrix.mapRect(mBounds);
            return mBounds;
        }
        return null;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        mMatrix.reset();
        final RectF bounds = getImageBounds();
        // 如果没有图片则不进行布局
        if (bounds == null) {
            return;
        }
        final int width = getWidth();
        final int height = getHeight();
        final float imageWidth = bounds.width();
        final float imageHeight = bounds.height();
        float scale = 1f;
        // 图片的宽度超出，但高度没有超出，缩小并以宽度适应
        if (imageWidth > width && imageHeight < height) {
            scale = width / imageWidth;
        }
        // 图片的高度超出，但宽度没有超出，缩小并以高度适应
        if (imageHeight > height && imageWidth < width) {
            scale = height / imageHeight;
        }
        // 图片宽度和高度都超出或都没有超出，缩小或放大适应
        if ((imageWidth > width && imageHeight > height)
                || (imageWidth < width && imageHeight < height)) {
            scale = Math.min(width / imageWidth, height / imageHeight);
        }
        mMinScale = scale;
        mMaxScale = scale * 4f;
        // 以图片中心为原点缩放图片
        mMatrix.postScale(scale, scale, bounds.centerX(), bounds.centerY());
        // 将缩放后的图片的中心移动到视图的中心
        mMatrix.postTranslate(getPivotX() - bounds.centerX(), getPivotY() - bounds.centerY());
        setImageMatrix(mMatrix);
    }


    private class OnGestureListenerImpl extends GestureDetector.SimpleOnGestureListener {
        /**
         * 判断移动距离是否达到最低限制
         */
        private boolean isMoveAction(float dx, float dy) {
            return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;
        }

        /**
         * 图片平移后调整图片位置
         *
         * @param width   视图宽度
         * @param height  视图高度
         * @param isLockX X方向移动是否锁定
         * @param isLockY Y方向移动是否锁定
         */
        private void adjustWhenTranslateAfter(int width, int height, boolean isLockX, boolean isLockY) {
            final RectF bounds = getImageBounds();
            // 如果没有图片则不进行调整
            if (bounds == null) {
                return;
            }
            float adjustX = 0f, adjustY = 0f;
            // 图片左边没有靠边，往左边移动
            if (!isLockX && bounds.left > 0) {
                adjustX = -bounds.left;
            }
            // 图片右边没有靠边，往右边移动
            if (!isLockX && bounds.right < width) {
                adjustX = width - bounds.right;
            }
            // 图片上边没有靠边，往上边移动
            if (!isLockY && bounds.top > 0) {
                adjustY = -bounds.top;
            }
            // 图片下边没有靠边，往下边移动
            if (!isLockY && bounds.bottom < height) {
                adjustY = height - bounds.bottom;
            }
            mMatrix.postTranslate(adjustX, adjustY);
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            if (mIsScaling || mIsAutoScaling) {
                return false;
            }
            if (isMoveAction(distanceX, distanceY)) {
                final RectF bounds = getImageBounds();
                // 如果没有图片则不进行平移
                if (bounds == null) {
                    return false;
                }
                final int width = getWidth();
                final int height = getHeight();
                final boolean isLockX = bounds.width() < width;
                final boolean isLockY = bounds.height() < height;
                // 根据滑动方向和距离进行平移
                mMatrix.postTranslate(isLockX ? 0f : -distanceX, isLockY ? 0f : -distanceY);
                // 图片平移后调整图片位置
                adjustWhenTranslateAfter(width, height, isLockX, isLockY);
                setImageMatrix(mMatrix);
                return true;
            }
            return false;
        }

        /**
         * 双击控件，图片恢复原始大小
         */
        @Override
        public boolean onDoubleTap(MotionEvent e) {
            if (mIsAutoScaling) {
                return false;
            }
            final float px = e.getX();
            final float py = e.getY();
            postDelayed(new AutoScaleRunnable(px, py, mMinScale), 16L);
            mIsAutoScaling = true;
            return true;
        }
    }

    private class OnScaleGestureListenerImpl implements OnScaleGestureListener {

        @Override
        public boolean onScale(@NonNull ScaleGestureDetector detector) {
            final float scale = getImageScale();
            float factor = detector.getScaleFactor();
            // 可以继续缩小或者可以继续放大
            if ((factor < 1.0f && scale > mMinScale) || (factor > 1.0f && scale < mMaxScale)) {
                // 如果缩小太多则调整
                if (scale * factor < mMinScale) {
                    factor = mMinScale / scale;
                }
                // 如果放大太多则调整
                if (scale * factor > mMaxScale) {
                    factor = mMaxScale / scale;
                }
                mMatrix.postScale(factor, factor, detector.getFocusX(), detector.getFocusY());
                // 图片缩放后调整图片位置
                adjustWhenScaleAfter();
                setImageMatrix(mMatrix);
                return true;
            }
            return false;
        }

        @Override
        public boolean onScaleBegin(@NonNull ScaleGestureDetector detector) {
            mIsScaling = true;
            return true;
        }

        @Override
        public void onScaleEnd(@NonNull ScaleGestureDetector detector) {
            mIsScaling = false;
        }
    }

    private class AutoScaleRunnable implements Runnable {
        /**
         * 缩小因子，每次缩小时，缩小的倍数
         */
        private static final float FACTOR_SMALLER = 0.96F;

        /**
         * 放大因子，每次放大时，放大的倍数
         */
        private static final float FACTOR_BIGGER = 1.08F;

        /**
         * 缩放中心的横坐标
         */
        private final float px;

        /**
         * 缩放中心的纵坐标
         */
        private final float py;

        /**
         * 缩放的目标倍数，分为多次缩放，递归达到目标大小
         */
        private final float scale;

        /**
         * 每次缩放延时
         */
        private final long delay;

        /**
         * 每次缩放因子
         */
        private final float factor;

        public AutoScaleRunnable(float px, float py, float scale) {
            this(px, py, scale, 16L);
        }

        public AutoScaleRunnable(float px, float py, float scale, long delay) {
            this.px = px;
            this.py = py;
            this.scale = scale;
            this.delay = delay;
            this.factor = scale < getImageScale() ? FACTOR_SMALLER : FACTOR_BIGGER;
        }

        /**
         * 执行一次缩放操作
         *
         * @param scale 本次缩放比例
         */
        private void doOnceScale(float scale) {
            mMatrix.postScale(scale, scale, px, py);
            adjustWhenScaleAfter();
            setImageMatrix(mMatrix);
        }

        @Override
        public void run() {
            doOnceScale(factor);
            float currentScale = getImageScale();
            if ((factor < 1.0f && currentScale > scale) || (factor > 1.0f && currentScale < scale)) {
                if (delay > 0) {
                    postDelayed(this, delay);
                } else {
                    post(this);
                }
            } else {
                doOnceScale(scale / currentScale);
                mIsAutoScaling = false;
            }
        }
    }

    @Override
    public boolean performClick() {
        return super.performClick();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                getParent().requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                performClick();
                getParent().requestDisallowInterceptTouchEvent(false);
                break;
        }
        return mGesture.onTouchEvent(event) || mScaleGesture.onTouchEvent(event) || super.onTouchEvent(event);
    }

    /**
     * 图片缩放后调整图片位置
     */
    private void adjustWhenScaleAfter() {
        final RectF bounds = getImageBounds();
        // 如果没有图片则不用调整
        if (bounds == null) {
            return;
        }
        final int width = getWidth();
        final int height = getHeight();
        final float imageWidth = bounds.width();
        final float imageHeight = bounds.height();
        float adjustX = 0f, adjustY = 0f;
        // 如果图片的宽度超出
        if (imageWidth >= width) {
            // 如果图片左边没有对齐，就往左边移动
            if (bounds.left > 0f) {
                adjustX = -bounds.left;
            }
            // 如果图片右边没有对齐，就往右边移动
            if (bounds.right < width) {
                adjustX = width - bounds.right;
            }
        }
        // 如果图片的高度超出
        if (imageHeight >= height) {
            // 如果图片上边没有对齐，就往上边移动
            if (bounds.top > 0f) {
                adjustY = -bounds.top;
            }
            // 如果图片下边没有对齐，就往下边移动
            if (bounds.bottom < height) {
                adjustY = height - bounds.bottom;
            }
        }
        // 如果图片的宽度没有超出，则让其水平居中
        if (imageWidth < width) {
            adjustX = width / 2f - bounds.right + imageWidth / 2f;
        }
        // 如果图片的高度没有超出，则让其垂直居中
        if (imageHeight < height) {
            adjustY = height / 2f - bounds.bottom + imageHeight / 2f;
        }
        mMatrix.postTranslate(adjustX, adjustY);
    }
}